package com.greate.community.quartz;

import com.greate.community.entity.DiscussPost;
import com.greate.community.service.DiscussPostService;
import com.greate.community.service.ElasticsearchService;
import com.greate.community.service.FollowService;
import com.greate.community.constant.CommunityConstant;
import com.greate.community.util.RedisKeyUtil;
import org.apache.commons.lang3.StringUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.util.HtmlUtils;

/**
 * 帖子分数计算刷新
 */
public class PostScoreRefreshJob implements Job, CommunityConstant {

    private static final Logger logger = LoggerFactory.getLogger(PostScoreRefreshJob.class);

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private DiscussPostService discussPostService;

    @Autowired
    private ElasticsearchService elasticsearchService;

    @Autowired
    private FollowService followService;

//    // Epoch 纪元
//    private static final Date epoch;
//
//    static {
//        try {
//            epoch = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2014-01-01 00:00:00");
//        } catch (ParseException e) {
//            throw new RuntimeException("初始化 Epoch 纪元失败", e);
//        }
//    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String redisKey = RedisKeyUtil.getPostScoreKey();
//        if (!redisTemplate.hasKey(redisKey)) {
//            logger.info("[Post Score Job Execution] no post need refresh");
//            return ;
//        }
        BoundSetOperations operations = redisTemplate.boundSetOps(redisKey);
        if (operations.size() == 0) {
            logger.info("[Post Score Job Execution] no post need refresh");
            return ;
        }

        logger.info("[Post Score Job Execution] refresh post score start: " + operations.size());
        while (operations.size() > 0) {
            this.refresh((Integer) operations.pop());
        }
        logger.info("[Post Score Job Execution] refresh post score over");
    }

    /**
     * 刷新帖子分数
     * @param postId
     */
    private void refresh(int postId) {
        DiscussPost post = discussPostService.findDiscussPostById(postId);

        if (post == null) {
            logger.error("该帖子不存在: id = " + postId);
            return ;
        }

//        // 是否加精
//        boolean wonderful = post.getStatus() == 1;
//        // 评论数量
//        int commentCount = post.getCommentCount();
//        // 点赞数量
//        long likeCount = post.getLikeCount();
//
//        // 计算权重
//        double w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2;
//        // 分数 = 权重 + 发帖距离天数
//        double score = Math.log10(Math.max(w, 1))
//                + (post.getCreateTime().getTime() - epoch.getTime()) / (1000 * 3600 * 24);
//        // 更新帖子分数
//        discussPostService.updateScore(postId, score);
//        // 同步搜索数据
//        post.setScore(score);
//        elasticsearchService.saveDiscussPost(post);


        //发帖作者
        int userId = post.getUserId();
        //关注该发帖作者的粉丝数量
        long followerCount = followService.findFollowerCount(3, userId);
        //帖子是否加精
        boolean wonderful = post.getStatus() == 1;
        //帖子评论数量
        int commentCount = post.getCommentCount();
        //帖子点赞数量
        long likeCount = post.getLikeCount();
        //帖子浏览量
        int viewCount = post.getViewCount();
        //帖子创建时间
        long createTime = post.getCreateTime().getTime();
        //当前时间
        long now = System.currentTimeMillis();

        String content = HtmlUtils.htmlUnescape(post.getContent());
        //帖子内容长度，最好还是要统计热门词汇数量
        int wordNums = content.length();
        String pic_urls = post.getPicUrls();
        //帖子配图数量
        int picNums = StringUtils.countMatches(pic_urls,"https://");

        /**
         * 初始热度值，与最后的热度值H呈 正相关 (不是正比)，
         * 根据发帖作者的 粉丝数量、帖子内容、帖子配图 来计算
         */
        double H_init = followerCount*4 + picNums*8 + (wordNums/50)*5;
        /**
         * 互动热度值，与最后的热度值H呈 负相关 (不是反比)，
         * 根据该帖子 是否加精、浏览量、点赞数、评论数 来计算
         */
        double H_interact = (wonderful ? 100 : 0) + viewCount*2 + likeCount*3 + commentCount*5;
        /**
         * 时间衰减因子，即 现在距帖子发布时间的时间距离，以小时计，在计算热度值H时加1是为了
         * 防止最新发表的会导致分母过小 ，这个值可调，在前期平台人数较少时调整大
         * 一些，在后期人数增长起来后，可以调整的小一些，比如，因为用户对平
         * 台资源有一个消化时间。并不是一发出来就有数据的
         */
        double H_time = (now - createTime) / (1000 * 3600);
        /**
         * 重力因子，它决定了热度随时间下降的速度，前期平台人员较少时，相对应得
         * 发的资源也会比较少，这时可以把G调小一点，减缓时间推移对热度下降的影响，
         * 后期平台人员增多时，相对应的资源也会增加，这时可以把G调大一点，加速时间
         * 推移对热度下降的影响。比如：1.2-1.8
         */
        double G = 0.8;
        //视不同情况，增大重力因子G，加速帖子热度下降
        if(H_init >= 200)
            G = 0.9;
        if(G == 0.9 && H_interact >= 200)
            G = 0.95;
        /**
         * 加减权重，初始值可以为0，在某些偶然或者极端情况下需要人为
         * 干预的时候，可以动态调整其值，使其能够及时的控制其热度
         */
        double Hweight = 0;

        /**
         * 热度值计算
         */
        double H_score = ((H_init + H_interact)/Math.pow(H_time + 1, G)) + Hweight;

        // 更新帖子分数
        discussPostService.updateScore(postId, H_score);
        // 同步搜索数据
        post.setScore(H_score);
        elasticsearchService.saveDiscussPost(post);
    }
}
